<?php
class Node_model_node extends Model {
    private $table_node = 'n_node';
    private $table_keyword = 'n_keyword';
    private $table_keyword_node = 'n_keyword_node';
    private $table_mapping = 'n_mapping';
    private $table_node_position = 'n_node_position';
    private $talbe_image = 'n_image';
    
    private static $filter_off_words = array(
        'off' => 1, 'of' => 1, 'on' => 1, 'a' => 1, 'an' => 1, 'is' => 1
    );
    
    
    public function add_node($data) {
        $caption = isset($data['caption']) ? trim($data['caption']) : '';
        if (empty($caption)) tos_die();
        
        $user_id = isset($data['user_id']) ? (int) $data['user_id'] : 0;
        if ($user_id <= 0) tos_die();
        
        $type = isset($data['type']) ? trim($data['type']) : '';
        if (empty($type)) tos_die();
        
        $parent_id = isset($data['parent_id']) ? (int) $data['parent_id'] : 0;
        $url_segment = isset($data['url_segment']) ? trim($data['url_segment']) : '';
        $url_segment = empty($url_segment) ? tos_clean_url_segment($caption) : tos_clean_url_segment($url_segment);
        if (0 < $this->has_url_segment($url_segment)) {
            $url_segment .= '-' . time() . rand();
        }
        
        $is_public = isset($data['is_public']) ? (int) $is_public : 0;
        $is_public = ($is_public == 1) ? 1 : 0;
        
        $description = isset($data['description']) ? trim($data['description']) : '';
        $thumbnail = isset($data['thumbnail']) ? trim($data['thumbnail']) : '';
        $rank = isset($data['rank']) ? (int) $data['rank'] : 0;
        
        $now = time();
        
        $db_data = array(
                'user_id'       => $user_id,
                'type'          => $type,
                'caption'       => $caption,
                'description'   => $description,
                'url_segment'   => $url_segment,
                'is_public'     => $is_public,
                'created'       => $now,
                'changed'       => $now,
                'thumbnail'     => $thumbnail,
                'parent_id'     => $parent_id,
                'rank'          => $rank
            );
        $db_data_format = array(
                'user_id'       => DB_DATA_INT,
                'type_id'       => DB_DATA_INT,
                'created'       => DB_DATA_INT,
                'changed'       => DB_DATA_INT,
                'is_public'     => DB_DATA_INT,
                'rank'          => DB_DATA_INT
            );
        $db = $this->db;
        $db->trans_start();
        $node_id = $db->insert($this->table_node, $db_data, $db_data_format);
        
        if ($node_id <= 0) tos_die();
        
        $words = isset($data['words']) ? $data['words'] : false;
        if ($words) {
            $this->add_words_to_node($words, $node_id, 1);
        }
        if ($caption) {
            $caption_words = tos_explode_keywords($caption, ' ');
            $caption_words[] = $type;
            $this->add_words_to_node($caption_words, $node_id, 0);
        }
        //$this->add_word($type, 0);
        $prev_ids = isset($data['prev_ids']) ? $data['prev_ids'] : false;
        if ($prev_ids) {
            $this->add_prev_ids($prev_ids, $node_id);
        }
        if ($parent_id > 0) {
            $this->child_hit_node($parent_id);
        }
        $db->trans_commit();
        return $node_id;
    }
    
    
    public function edit_node($node_id, $data) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        $caption = isset($data['caption']) ? trim($data['caption']) : '';
        if ( ! empty($caption)) {
            $db_data['caption'] = $caption;
        }
        $old_parent_id = false;
        $new_parent_id = false;
        if (isset($data['parent_id'])) {
            $new_parent_id = (int) $data['parent_id'];
            $old_parent_id = $this->get_parent_id($node_id);
            if ($new_parent_id != $old_parent_id) {
                $db_data['parent_id'] = $new_parent_id;
            }
        }
        $url_segment = isset($data['url_segment']) ? trim($data['url_segment']) : false;
        if ($url_segment !== false) {
            if ( ! empty($url_segment)) {
                $url_segment = tos_clean_url_segment($url_segment);
                $has_id = $this->has_url_segment($url_segment);
                if ($has_id > 0 && $has_id !== $node_id) {
                    $url_segment .= '-' . time() . rand();
                }
                $db_data['url_segment'] = $url_segment;
            }
        }
        if (isset($data['is_public'])) {
            $db_data['is_public'] = (1 == (int) $data['is_public']) ? 1 : 0;
        }
        
        if (isset($data['description'])) {
            $db_data['description'] = trim($data['description']);
        }
        if (isset($data['thumbnail'])) {
            $db_data['thumbnail'] = trim($data['thumbnail']);
        }
        if (isset($data['rank'])) {
            $db_data['rank'] = (int) $data['rank'];
        }
        $db_data['changed'] = time();
        $db_data_format = array(
                'user_id'       => DB_DATA_INT,
                'type_id'       => DB_DATA_INT,
                'created'       => DB_DATA_INT,
                'changed'       => DB_DATA_INT,
                'is_public'     => DB_DATA_INT,
                'rank'          => DB_DATA_INT
            );
        $this->db->update($this->table_node, $db_data, array('id' => $node_id), $db_data_format, array('id' => DB_DATA_INT));
        
        $words = isset($data['words']) ? $data['words'] : false;
        if ($words) {
            $this->add_words_to_node($words, $node_id, 1);
        }
        if ($old_parent_id) {
            $this->child_hit_node($old_parent_id, '-');
        }
        if (isset($db_data['parent_id'])) {
            $this->child_hit_node($db_data['parent_id']);
        }
        $prev_ids = isset($data['prev_ids']) ? $data['prev_ids'] : false;
        if ($prev_ids) {
            $this->add_prev_ids($prev_ids, $node_id);
        }
        $next_ids = isset($data['next_ids']) ? $data['next_ids'] : false;
        if ($next_ids) {
            $this->add_next_ids($next_ids, $node_id);
        }
        $remove_prev_ids = isset($data['remove_prev_ids']) ? $data['remove_prev_ids'] : false;
        if ($remove_prev_ids) {
            $this->remove_prev_ids($remove_prev_ids, $node_id);
        }
        $remove_next_ids = isset($data['remove_next_ids']) ? $data['remove_next_ids'] : false;
        if ($remove_next_ids) {
            $this->remove_next_ids($remove_next_ids, $node_id);
        }
        $remove_keyword_ids = isset($data['remove_keyword_ids']) ? $data['remove_keyword_ids'] : false;
        if ($remove_keyword_ids) {
            $this->remove_ksn($remove_keyword_ids, $node_id);
        }
    }
    
    
    public function remove_node($node_id) {
        $parent_id = $this->get_parent_id($node_id);
        if ($parent_id > 0) {
            $this->child_hit_node($parent_id, '-');
        }
        if ($this->db->delete($this->table_node, array('id' => $node_id) )) {
            $this->clear_ksn($node_id);
            $this->clear_prev_ids($node_id);
            $this->clear_next_ids($node_id);
        }
        return $parent_id;
    }
    
    
    public function has_url_segment($url_segment) {
        $url_segment = (string) $url_segment;
        
        $row = $this->db->select_row(
            $this->table_node,
            array('id'),
            array('url_segment' => $url_segment)
        );
        if ($row) {
            return (int)$row['id'];
        } else {
            return 0;
        }
    }
    
    
    public function get_caption($node_id) {
        static $captions = array();
        $node_id = (int) $node_id;
        if ($node_id <= 0) return false;
        
        if ( ! isset($captions[$node_id])) {
            $row = $this->db->select_row(
                $this->table_node,
                array('id', 'caption', 'url_segment'),
                array('id' => $node_id),
                array('id' => DB_DATA_INT)
            );
            if ($row) {
                $captions[$node_id] = $row;
            } else {
                return false;
            }
        }
        return $captions[$node_id];
    }
    
    
    public function get_url_segment($node_id) {
        static $url_segments = array();
        $node_id = (int) $node_id;
        if ($node_id <= 0) return '';
        
        if ( ! isset($url_segment[$node_id])) {
            $row = $this->db->select_row(
                $this->table_node,
                array('url_segment'),
                array('id' => $node_id),
                array('id' => DB_DATA_INT)
            );
            if ($row) {
                $url_segments[$node_id] = $row['url_segment'];
            } else {
                tos_die();
            }
        }
        return $url_segments[$node_id];
    }
    
    
    public function get_url($node_id) {
        $node = $this->db->select_row(
            $this->table_node,
            array('parent_id', 'url_segment'),
            array('id' => $node_id),
            array('id' => DB_DATA_INT)
        );
        if ( ! $node) tos_die();
        
        $parent_id = (int) $node['parent_id'];
        $parent_url_segment = '';
        if ($parent_id > 0) {
            $parent_url_segment = $this->get_url_segment($parent_id) . '/';
        }
        return $parent_url_segment . $node['url_segment'];
    }
    
    
    public function get_parent_id($node_id) {
        $row = $this->db->select_row(
            $this->table_node,
            array('parent_id'),
            array('id' => $node_id),
            array('id' => DB_DATA_INT)
        );
        if ($row) {
            return (int) $row['parent_id'];
        } else {
            return 0;
        }
    }
    
    
    public function get_node_by_id($node_id) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        return $this->db->select_row(
            $this->table_node,
            array(),
            array('id' => $node_id),
            array('id' => DB_DATA_INT)
        );
    }
    
    
    public function get_node_by_url_segment($url_segment) {
        $url_segment = trim($url_segment);
        
        return $this->db->select_row(
            $this->table_node,
            array(),
            array('url_segment' => $url_segment)
        );
    }
    
    
    public function prev_hit_node($node_id, $operator = '+') {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        $operator = ($operator === '-') ? '-' : '+';
        
        return $this->db->update(
            $this->table_node,
            array('prev_hit' => "prev_hit{$operator}1"),
            array('id' => $node_id),
            array('prev_hit' => DB_DATA_SYS),
            array('id' => DB_DATA_INT)
        );
    }
    
    
    public function next_hit_node($node_id, $operator = '+') {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        $operator = ($operator === '-') ? '-' : '+';
        return $this->db->update(
            $this->table_node,
            array('next_hit' => "next_hit{$operator}1"),
            array('id' => $node_id),
            array('next_hit' => DB_DATA_SYS),
            array('id' => DB_DATA_INT)
        );
    }
    
    
    public function child_hit_node($node_id, $operator = '+') {
        $node_id = (int) $node_id;
        if ($node_id <= 0) return false;
        $operator = ($operator === '-') ? '-' : '+';
        return $this->db->update(
            $this->table_node,
            array('child_hit' => "child_hit{$operator}1"),
            array('id' => $node_id),
            array('child_hit' => DB_DATA_SYS),
            array('id' => DB_DATA_INT)
        );
    }
    
    
    public function get_nodes($offset = 0, $limit = 20) {
        return $this->db->select_rows(
            $this->table_node,
            array(),
            array(),
            array(),
            array('rank' => 'DESC', 'changed' => 'DESC'),
            $offset, $limit
        );
    }
    
    
    public function get_count() {
        $sql = "SELECT count(*) count FROM `{$this->table_node}` LIMIT 1";
        $row = $this->db->row($sql);
        if ($row) {
            return (int) $row['count'];
        } else {
            return 0;
        }
    }
    
    
    public function get_nodes_by_search($query = '', $offset = 0, $limit = 11) {
        $query = trim($query);
        if (empty($query)) {
            return $this->get_nodes($offset, $limit);
        } else {
            return array();
        }
    }
    
    public function get_nodes_hot_hit($offset = 0, $limit = 7) {
        $offset = (int) $offset;
        $limit = (int) $limit;
        
        $sql = "SELECT * FROM `{$this->table_node}` ORDER BY `child_hit` DESC LIMIT $offset, $limit";
        return $this->db->rows($sql);
    }
    
    
    public function get_nodes_by_keyword_id($keyword_id, $offset = 0, $limit = 11) {
        $keyword_id = (int) $keyword_id;
        if ($keyword_id <= 0) return false;
        $offset = (int) $offset;
        $limit = (int) $limit;
        $limit = ($limit <= 0) ? 20 : $limit;
        
        $sql = "SELECT `n`.* FROM `{$this->table_node}` `n` " .
                "INNER JOIN `{$this->table_keyword_node}` `kn` ON `kn`.`node_id`=`n`.`id` " .
                "WHERE `kn`.`keyword_id`={$keyword_id} ORDER BY `n`.`rank` DESC, `n`.`child_hit` DESC, `n`.`next_hit` DESC, `n`.`changed` DESC LIMIT $offset,$limit";
        return $this->db->rows($sql);
    }
    
    
    public function get_nodes_by_keyword_ids($keyword_ids, $offset = 0, $limit = 11) {
        $offset = (int) $offset;
        $limit = (int) $limit;
        $limit = ($limit <= 0) ? 20 : $limit;
        if (empty($keyword_ids)) {
            return $this->get_nodes($offset, $limit);
        } else {
            $keyword_ids_sql  = '(' . implode(',', $keyword_ids) . ')';
            $sql = "SELECT `n`.* FROM `{$this->table_node}` `n` " .
                "INNER JOIN `{$this->table_keyword_node}` `kn` ON `kn`.`node_id`=`n`.`id` " .
                "WHERE `kn`.`keyword_id` in {$keyword_ids_sql} ORDER BY `n`.`rank` DESC, `n`.`child_hit` DESC, `n`.`next_hit` DESC, `n`.`changed` DESC LIMIT $offset,$limit";
            return $this->db->rows($sql);
        }
    }
    
    
    public function get_nodes_by_word($word, $offset = 0, $limit = 11) {
        $word = trim($word);
        $word = strtolower($word);
        $keyword_id = $this->has_word($word);
        return $this->get_nodes_by_keyword_id($keyword_id);
    }
    
    public function get_nodes_by_parent_id($parent_id, $offset = 0, $limit = 11) {
        return $this->db->select_rows(
            $this->table_node,
            array(),
            array('parent_id' => (int) $parent_id),
            array('parent_id' => DB_DATA_INT),
            array('rank' => 'DESC', 'child_hit' => 'DESC', 'next_hit' => 'DESC', 'changed' => 'DESC'),
            $offset, $limit
        );
    }
    
    public function get_count_by_parent_id($parent_id) {
        $parent_id = (int) $parent_id;
        $sql = "SELECT count(*) count FROM `{$this->table_node}` WHERE `parent_id`='{$parent_id}'";
        $row = $this->db->row($sql);
        if ($row) {
            return (int) $row['count'];
        }
        return 0;
    }
    
    public function get_next_nodes($node_id, $offset = 0, $limit = 11) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        $offet = (int) $offset;
        $limit = (int) $limit;
        
        $offset = ($offset <= 0) ? 0 : $offset;
        $limit = ($limit <= 0) ? 20 : $limit;
        
        $table_mapping = $this->table_mapping;
        $table_node = $this->table_node;
        
        $sql = "SELECT `n`.* FROM `{$table_node}` `n` INNER JOIN `{$table_mapping}` `m` " . 
            "ON `n`.`id`=`m`.`next_id` WHERE `m`.`prev_id` = {$node_id} ORDER BY `n`.`rank` DESC, `n`.`child_hit` DESC, `n`.`next_hit` DESC, `n`.`changed` DESC LIMIT {$offset}, {$limit}";
        return $this->db->rows($sql);
    }
    
    
    public function get_prev_nodes($node_id, $offset = 0, $limit = 20) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        $offet = (int) $offset;
        $limit = (int) $limit;
        
        $offset = ($offset <= 0) ? 0 : $offset;
        $limit = ($limit <= 0) ? 20 : $limit;
        
        $table_mapping = $this->table_mapping;
        $table_node = $this->table_node;
        
        $sql = "SELECT `n`.* FROM `{$table_node}` `n` INNER JOIN `{$table_mapping}` `m` " . 
            "ON `n`.`id`=`m`.`prev_id` WHERE `m`.`next_id` = {$node_id} ORDER BY `n`.`rank` DESC, `n`.`child_hit` DESC, `n`.`next_hit` DESC, `n`.`changed` DESC LIMIT {$offset}, {$limit}";
        return $this->db->rows($sql);
    }
    
    
    public function add_word($word, $is_label = 0) {
        $word = trim($word);
        if (isset(self::$filter_off_words[$word])) {
            return 0;
        }
        if (empty($word)) { 
            return 0;
        }
        $keyword_id = $this->has_word($word);
        if ($keyword_id <= 0) {
            $keyword_id = $this->db->insert(
                $this->table_keyword,
                array('word' => $word, 'is_label' => $is_label),
                array('is_label' => DB_DATA_INT)
            );
        } else {
            $this->db->update(
                $this->table_keyword,
                array('is_label' => $is_label),
                array('id' => $keyword_id),
                array('is_label' => DB_DATA_INT),
                array('id' => DB_DATA_INT)
            );
        }
        if ($keyword_id <= 0) tos_die();
        return $keyword_id;
    }
    
    
    public function has_word($word) {
        if (empty($word)) tos_die();
        
        $row = $this->db->select_row(
            $this->table_keyword,
            array('id'),
            array('word' => $word)
        );
        
        if ($row) {
            return (int) $row['id'];
        } else {
            return 0;
        }
    }
    
    
    public function search_keywords($word = '', $offset = 0, $limit = 7) {
        $word = trim($word);
        $offset = (int) $offset;
        $limit = (int) $limit;
        $sql = "SELECT * FROM `{$this->table_keyword}` WHERE `word` LIKE '{$word}%' LIMIT $offset, $limit";
        return $this->db->rows($sql);
    }
    
    
    public function hit_keyword($keyword_id, $operator = '+') {
        $keyword_id = (int) $keyword_id;
        if ($keyword_id <= 0) tos_die();
        $operator = ($operator === '-') ? '-' : '+';
        
        return $this->db->update(
            $this->table_keyword,
            array('hit' => "hit{$operator}1"),
            array('id' => $keyword_id),
            array('hit' => DB_DATA_SYS),
            array('id' => DB_DATA_INT)
        );
    }
    
    
    public function add_words_to_node($words, $node_id, $is_label = 0) {
        if ( ! $words) tos_die();
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        foreach ($words as $word) {
            $keyword_id = $this->add_word($word, $is_label);
            if ($keyword_id > 0) {
                $this->add_kn($keyword_id, $node_id);
            }
        }
    }
    
    
    public function get_keyword_by_word($label) {
        $label = trim($label);
        if (empty($label)) tos_die();
        
        return $this->db->select_row(
            $this->table_keyword,
            array(),
            array('word'=>$label),
            array('is_label' => DB_DATA_INT)
        );
    }
    
    
    public function get_keywords_like_word($word, $offset = 0, $limit = 14) {
        $word = trim($word);
        $word = strtolower($word);
        $offset = (int) $offset;
        $limit = (int) $limit;
        
        $sql = "SELECT * FROM `{$this->table_keyword}` WHERE `word` " . 
           "LIKE '%{$word}' ORDER BY `hit` DESC LIMIT $offset, $limit";
        return $this->db->rows($sql);
    }
    
    public function get_keywords_from_node($node_id, $is_label = 1) {
        static $keywords_arr = array();
        
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        $is_label = (int) $is_label;
        
        $key = $node_id . '_' . $is_label;
        
        if ( ! isset($keywords_arr[$key])) {
            $table_keyword = $this->table_keyword;
            $table_keyword_node = $this->table_keyword_node;
            
            $is_label_str = '';
            if ($is_label === 1) {
                $is_label_str = " AND `k`.`is_label`={$is_label}";
            } 
            $sql = "SELECT `k`.* FROM `{$table_keyword}` `k` INNER JOIN `{$table_keyword_node}` `kn` " .
                    "ON `k`.`id`=`kn`.`keyword_id` WHERE `kn`.`node_id`={$node_id}{$is_label_str}";
            $keywords_arr[$key] = $this->db->rows($sql);
        }
        
        return $keywords_arr[$key];
    }
    
    
    // kn : keyword_id - node_id
    public function add_kn($keyword_id, $node_id) {
        $kn_id = $this->has_kn($keyword_id, $node_id);
        if ($kn_id <= 0) {
            $kn_id = $this->db->insert(
                $this->table_keyword_node,
                array('keyword_id' => $keyword_id, 'node_id' => $node_id),
                array('keyword_id' => DB_DATA_INT, 'node_id' => DB_DATA_INT)
            );
            $this->hit_keyword($keyword_id);
        }
        if ($kn_id <= 0) tos_die();
        return $kn_id;
    }
    
    
    public function has_kn($keyword_id, $node_id) {
        $keyword_id = (int) $keyword_id;
        $node_id = (int) $node_id;
        if ($keyword_id <= 0) tos_die();
        if ($node_id <= 0) tos_die();
        
        $row = $this->db->select_row(
            $this->table_keyword_node,
            array('id'),
            array('keyword_id' => $keyword_id, 'node_id' => $node_id),
            array('keyword_id' => DB_DATA_INT, 'node_id' => DB_DATA_INT)
        );
        if ($row) {
            return (int) $row['id'];
        } else {
            return 0;
        }
    }
    
    
    
    public function remove_ksn($keyword_ids, $node_id) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        foreach ($keyword_ids as $keyword_id) {
            $keyword_id = (int) $keyword_id;
            if ($keyword_id > 0) {
                $this->remove_kn($keyword_id, $node_id);
            }
        }
    }
    
    
    public function clear_ksn($node_id) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        $table_keyword = $this->table_keyword;
        $table_keyword_node = $this->table_keyword_node;
        
        $sql = "UPDATE `{$table_keyword}` `k` INNER JOIN `{$table_keyword_node}` `kn` " .
            "ON `kn`.`keyword_id` = `k`.`id` " .
            "SET hit=hit-1 " .
            "WHERE `kn`.`node_id` = {$node_id}";
        //echo $sql;
        
        $this->db->delete(
            $this->table_keyword_node,
            array('node_id' => $node_id),
            array('node_id' => DB_DATA_INT)
        );
    }
    
    
    public function remove_kn($keyword_id, $node_id) {
        $kn_id = $this->has_kn($keyword_id, $node_id);
        if ($kn_id > 0) {
            $this->db->delete(
                $this->table_keyword_node,
                array('id' => $kn_id),
                array('id' => DB_DATA_INT)
            );
            $this->hit_keyword($keyword_id, '-');
        }
    }
    
    
    public function add_prev_ids($prev_ids, $node_id) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        foreach ($prev_ids as $prev_id) {
            $prev_id = (int) $prev_id;
            if ($prev_id > 0) {
                $this->add_mapping($prev_id, $node_id);
            }
        }
    }
    
    
    public function remove_prev_ids($prev_ids, $node_id) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        foreach ($prev_ids as $prev_id) {
            $prev_id = (int) $prev_id;
            if ($prev_id > 0) {
                $this->remove($prev_id, $node_id);
            }
        }
    }
    
    
    public function clear_prev_ids($node_id) {
        return $this->db->delete(
            $this->table_mapping,
            array('next_id' => $node_id),
            array('next_id' => DB_DATA_INT)
        );
    }
    
    
    public function add_next_ids($next_ids, $node_id) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        foreach ($next_ids as $next_id) {
            $next_id = (int) $next_id;
            if ($next_id > 0) {
                $this->add_mapping($node_id, $next_id);
            }
        }
    }
    
    
    public function remove_next_ids($next_ids, $node_id) {
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        foreach ($next_ids as $next_id) {
            $next_id = (int) $next_id;
            if ($next_id > 0) {
                $this->remove_mappign($node_id, $next_id);
            }
        }
    }
    
    
    public function clear_next_ids($node_id) {
        return $this->db->delete(
            $this->table_mapping,
            array('prev_id' => $node_id),
            array('prev_id' => DB_DATA_INT)
        );
    }
    
    
    public function has_mapping($prev_id, $next_id) {
        $prev_id = (int) $prev_id;
        $next_id = (int) $next_id;
        if ($prev_id <= 0) tos_die();
        if ($next_id <= 0) tos_die();
        
        $row = $this->db->select_row(
            $this->table_mapping,
            array('id'),
            array('prev_id' => $prev_id, 'next_id' => $next_id),
            array('prev_id' => DB_DATA_INT, 'next_id' => DB_DATA_INT)
        );
        if ($row) {
            return (int) $row['id'];
        } else {
            return 0;
        }
    }
    
    
    public function add_mapping($prev_id, $next_id) {
        $mapping_id = $this->has_mapping($prev_id, $next_id);
        if ($mapping_id <= 0) {
            $mapping_id = $this->db->insert(
                $this->table_mapping,
                array('prev_id' => $prev_id, 'next_id' => $next_id),
                array('prev_id' => DB_DATA_INT, 'next_id' => DB_DATA_INT)
            );
            if ($mapping_id > 0) {
                $this->next_hit_node($prev_id);
                $this->prev_hit_node($next_id);
            }
        }
        if ($mapping_id <= 0) tos_die();
        return $mapping_id;
    }
    
    
    public function remove_mapping($prev_id, $next_id) {
        $mapping_id = $this->has_mapping($prev_id, $next_id);
        if ($mapping_id > 0) {
            if ($this->db->delete(
                $this->table_mappign,
                array('id' => $mapping_id)
            )) {
                $this->next_hit_node($prev_id, '-');
                $this->prev_hit_node($node_id, '-');
            }
        }
    }
    
    
    public function get_menu_item($node_id) {
        static $captions = array();
        $node_id = (int) $node_id;
        if ($node_id <= 0) return false;
        
        if ( ! isset($captions[$node_id])) {
            $row = $this->db->select_row(
                $this->table_node,
                array('id', 'parent_id', 'caption', 'url_segment'),
                array('id' => $node_id),
                array('id' => DB_DATA_INT)
            );
            if ($row) {
                $captions[$node_id] = $row;
            } else {
                return false;
            }
        }
        return $captions[$node_id];
    }
    
    public function get_node_nav($node_id) {
        static $navs = array();
        $node_id = (int) $node_id;
        if ($node_id <= 0) tos_die();
        
        if ( ! isset($navs[$node_id])) {
            $menu_items = array();
            $item = array();
            $item['parent_id'] = 1;
            $current_node_id = $node_id;
            
            while ($current_node_id > 0) {
                $menu_item = $this->get_menu_item($current_node_id);
                if ($menu_item) {
                    array_unshift($menu_items, $menu_item);
                    $current_node_id = (int) $menu_item['parent_id'];
                } else {
                    $current_node_id = 0;
                }
            }
            $navs[$node_id] = $menu_items;
        }
        
        
        return $navs[$node_id];
    }
    
    public function get_menu_items($parent_id, $offset = 0, $limit = 10) {
        $parent_id = (int) $parent_id;
        if ($parent_id <= 0) tos_die();
        $offset = (int) $offset;
        $limit = (int) $limit;
        
        return $this->db->select_rows(
            $this->table_node,
            array('id', 'parent_id', 'url_segment', 'caption'),
            array('parent_id' => $parent_id),
            array(),
            array('rank' => 'DESC', 'child_hit' => 'DESC', 'next_hit' => 'DESC', 'changed' => 'DESC'),
            $offset, $limit
        );
    }
}
?>